(function($) {
	var uid = 0;
	function MusicData(parseResult) {
		var t = uid;
		this.uid = t;
		this.url = parseResult.url;
		this.title = parseResult.title;
		this.html = parseResult.html;
		this.$plainNode = parseResult.$oldNode;
		this.$cirnoNode = parseResult.$node;
	}
	MusicData.prototype.uid = undefined;
	MusicData.prototype.$cirnoNode = undefined;
	MusicData.prototype.$plainNode = undefined;
	MusicData.prototype.url = undefined;
	MusicData.prototype.title = undefined;
	MusicData.prototype.html = undefined;

	var mp3Pattern = /\.mp3$/i;
	function parseMp3(uid, $node) {
		var href = $node[0]["href"];
		var title = $node.attr("title");
		var html = $node.html();
		if (href && mp3Pattern.test(href)) {
			return {
				$node : $("<a/>").attr("id", "cirnoplayerLink-" + uid)
						.addClass("cirnoplayer").attr("href", "javascript:;")
						.attr("name", "cirnoplayerLink-" + uid).attr("title",
								title).html(html),
				$oldNode : $node,
				url : href,
				title : title,
				html : html
			};

		} else {
			return null;
		}
	}

	var $playerNode = undefined;
	var STATUS_STOP = "stopped";
	var STATUS_PLAY = "playing";
	var STATUS_PAUSE = "paused";
	var status = STATUS_STOP;
	var current = -1;
	var content = [];

	function _push(data) {
		content.push(data);
	}

	function _get(uid) {
		for ( var i = 0, max = content.length; i < max; i++) {
			if (content[i].uid == uid) {
				return i;
			}
		}
		return -1;
	}

	function _remove(uid) {
		for ( var i = 0, max = content.length; i < max; i++) {
			var data = content[i];
			if (data.uid == uid) {
				if (current >= i) {
					current--;
				}
				content.splice(i, 1);
				if (content.length == 0) {
					_reset();
				} else {
					updatePlayer();
				}
				return data;
			}
		}
	}

	function _reset() {
		status = STATUS_STOP;
		current = -1;
		updatePlayer();
		if ($playerNode) {
			$playerNode.jPlayer("stop");
		}
	}

	function _setPos(pos) {
		if (pos < 0 || pos >= content.length) {
			_reset();
		} else {
			current = pos;
			if (status == STATUS_PLAY) {
				_play();
			} else {
				_stop();
			}
		}
	}

	function _move(step) {
		var len = content.length;
		if (len == 0) {
			_reset();
		} else {
			var pos = current + step;
			while (pos < 0) {
				pos = len + pos;
			}
			while (pos >= len) {
				pos = pos - len;
			}
			_setPos(pos);
		}
		updatePlayer();
	}

	function _prev() {
		_move(-1);
	}

	function _next() {
		_move(1);
	}

	function _play() {
		if ($playerNode) {
			if (current >= 0) {
				status = STATUS_PLAY;
				$playerNode.jPlayer("setMedia", {
					mp3 : content[current].url
				});
				$playerNode.jPlayer("play");
				updatePlayer();
			} else {
				_reset();
			}
		} else {
			$.error("Player not ready");
		}
	}

	function _stop() {
		if ($playerNode) {
			status = STATUS_STOP;
			$playerNode.jPlayer("stop");
			updatePlayer();
		} else {
			$.error("Player not ready");
		}
	}

	function _pause() {
		if ($playerNode) {
			if (current >= 0) {
				$playerNode.jPlayer("pause");
				status = STATUS_PAUSE;
				updatePlayer();
			}
		} else {
			$.error("Player not ready");
		}
	}

	function _resume() {
		if ($playerNode) {
			if (current >= 0 && status == STATUS_PAUSE) {
				status = STATUS_PLAY;
				$playerNode.jPlayer("play");
				updatePlayer();
			}
		} else {
			$.error("Player not ready");
		}
	}

	function _togglePause() {
		if (status == STATUS_PLAY) {
			_pause();
		} else if (status == STATUS_PAUSE) {
			_resume();
		}
	}

	function updatePlayer() {
		for ( var i = 0, max = content.length; i < max; i++) {
			content[i].$cirnoNode.removeClass("cirnoplayerPlaying")
					.removeClass("cirnoplayerPaused").removeClass(
							"cirnoplayerStopped");
		}
		if (current >= 0) {
			if (status == STATUS_PLAY) {
				content[current].$cirnoNode.addClass("cirnoplayerPlaying");
			} else if (status == STATUS_PAUSE) {
				content[current].$cirnoNode.addClass("cirnoplayerPaused");
			} else if (status == STATUS_STOP) {
				content[current].$cirnoNode.addClass("cirnoplayerStopped");
			}
		}
		updateExt();
	}

	var onUpdate = [];

	function updateExt() {
		if (current >= 0) {
			$(".cirnoplayer-status-link").attr("href",
					"#cirnoplayerLink-" + content[current].uid).attr("title",
					content[current].title);
			$(".cirnoplayer-status-name").html(content[current].html);
			$(".cirnoplayer-status").text(status);
		} else {
			$(".cirnoplayer-status-link").attr("href", "javascript:;");
			$(".cirnoplayer-status-name").html("...");
			$(".cirnoplayer-status").text(status);
		}
		for ( var i = 0, max = onUpdate.length; i < max; i++) {
			onUpdate[i].fun(current >= 0 ? content[current] : undefined,
					status, onUpdate[i].params);
		}
	}
	window.setInterval(updateExt, 1000);

	paramsTemplate = {
		autoPlay : true,
		target : "#cirnoplayer",
		initialNodes : "a",
		initialNodeMaker : parseMp3,
		resourceUrl : "/cirnoplayer",
	};

	var methods = {
		init : function(params) {
			params = $.extend({}, paramsTemplate, params);
			$.cirnoplayer("add", params.initialNodes, params.initialNodeMaker);
			var _ready = params.ready;
			$(params.target).jPlayer({
				size : {
					width : 0,
					height : 0
				},
				ready : function() {
					$playerNode = $(this);
					$(this).bind($.jPlayer.event.ended, function(event) {
						if (status == STATUS_PLAY) {
							_next();
						}
					});
					if (params.autoPlay) {
						_next();
						_play();
					}
					if (_ready) {
						_ready();
					}
				},
				swfPath : params.resourceUrl
			});
		},
		update : function(fun, params) {
			onUpdate.push({
				fun : fun,
				params : params
			});
		},
		unupdate : function(fun) {
			for ( var i = 0, max = onUpdate.length; i < max; i++) {
				if (onUpdate[i].fun == fun) {
					var ret = onUpdate[i];
					onUpdate.splice(i, 1);
					return ret;
				}
			}
		},
		add : function(nodes, nodeMaker) {
			if (!nodes) {
				nodes = $("a");
			}

			if (!nodeMaker) {
				nodeMaker = parseMp3;
			}

			$(nodes)
					.each(
							function() {
								uid++;
								var $this = $(this);
								var result = nodeMaker(uid, $this, parseMp3);
								if (result) {
									var data = new MusicData(result);
									data.$cirnoNode
											.click(function() {
												var pos = _get(data.uid);
												if (pos >= 0) {
													if (status == STATUS_STOP) {
														_setPos(pos);
														_play();
													} else if (pos == current
															&& (status == STATUS_PAUSE || status == STATUS_PLAY)) {
														_togglePause();
													} else {
														_setPos(pos);
														_play();
													}
												}
											});
									_push(data);
									data.$plainNode.before(data.$cirnoNode)
											.detach();
								}
							});
		},
		remove : function(nodes) {
			$(nodes).each(
					function() {
						// Not tested...
						var $this = $(this);
						var id = $this.attr("id");
						// If it is a player
						if (id.length > 16
								&& id.substring(0, 15) == "cirnoplayerLink") {
							var uid = id.substring(16);
							var pos = get(uid);
							if (pos >= 0) {
								var data = content[pos];
								data.$cirnoNode.before(data.$plainNode)
										.remove();
							}
						}
					});
		},
		play : function(uid) {
			if (uid) {
				var pos = _get(uid);
				if (pos >= 0) {
					_setPos(pos);
					_play();
				}
			} else {
				if (status == STATUS_STOP) {
					if (current < 0) {
						_next();
					}
					_play();
				} else {
					_togglePause();
				}
			}
		},
		next : function() {
			_next();
		},
		prev : function() {
			_prev();
		},
		togglePause : function() {
			_togglePause();
		},
		stop : function() {
			_stop();
		},
		reset : function() {
			_reset();
		},
		pause : function() {
			_pause();
		},
		resume : function() {
			_resume();
		},
		status : function() {
			return status;
		}
	};

	$.cirnoplayer = function(method) {
		if (methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(
					arguments, 1));
		} else if (typeof method === 'object' || !method) {
			return methods.init.apply(this, arguments);
		} else {
			$.error('Method ' + method
					+ ' does not exist on jQuery.cirnoplayer');
		}
	};
})(jQuery);
